home *** CD-ROM | disk | FTP | other *** search
/ Software Vault: The Gold Collection / Software Vault - The Gold Collection (American Databankers) (1993).ISO / cdr49 / 262_01.zip / DEBUG.TXT < prev    next >
Text File  |  1993-04-14  |  21KB  |  608 lines

  1. .he /page #
  2. .ce 2
  3. Add a Source Debugger to Your C compiler
  4. by Robert Ramey
  5.  
  6. I really needed a better way of debugging my C programs.  I am
  7. happy with my compiler but it doesn't include a source level
  8. debugger.  Assembler language debuggers are difficult to use
  9. with compiled programs.   After a little thought and some more work
  10. I developed the source debugger you see in this article.  It:
  11.  
  12.  - is written entirely in C (except for 7 assembler language
  13. statements).  Thus it should be transportable to other environments.
  14.  
  15.  - does not requiere that programs to be analyzed be recompiled.
  16. They must be relinked however.
  17.  
  18.  - can trace and display program operation including entry of
  19. functions with display of arguments and return values.
  20.  
  21.  - can set break points at any function entry.
  22.  
  23.  - can display function arguments and global variables using any
  24. convenient format.  Can also display data structures pointed to
  25. by arguments and global variables.
  26.  
  27.  - can be used to analyze programs written in other languages
  28. that use similar argument passing and calling conventions.
  29.  
  30. This debugger cannot display local variables or trace or set
  31. break points which are not function entry points.
  32. Also the debugger can trace only the first level of a
  33. recurrsively called function.
  34. To overcome these limitations would have made the project much
  35. bigger and in many cases would have requiered fiddling with the
  36. compiler.  At least for now,  I decided it wasn't worth it.
  37.  
  38. 1. How to Use the Debugger
  39.  
  40. Use of the debugger will vary somewhat from system to system.  The
  41. description here applies to my computing environment.
  42. This consists of a SB-180 single board computer,
  43. 386k floppy, and a Beehive terminal.  I use CP/M compatible ZRDOS,
  44. ZAS assembler, ZLINK linker and Q/C C compiler.
  45.  
  46. Suppose I want debug the program HELLO which prints a simple
  47. message on the screen and terminates.
  48.  
  49.     #include <stdio>
  50.     main()
  51.     {
  52.         printf("Hello World\n");
  53.     }
  54.  
  55. Normally I link this program with the command:
  56.  
  57.     ZLINK HELLO,CRUNLIB/
  58.  
  59. and execute with the following:
  60.  
  61.     HELLO
  62.  
  63. When I want to analyze and control execution with my debugger I
  64. link with the command:
  65.  
  66.     ZLINK DEBUG,HELLO,CRUNLIB/ $S
  67.  
  68. and execute with:
  69.  
  70.     DEBUG
  71.  
  72. On encountering $S in the link command line, ZLINK generates a symbol
  73. table named DEBUG.SYM for the resulting linked program.  This
  74. symbol table is key to the debugging process.
  75. Before starting execution of the main() function,
  76. the symbol file is read, the debugger
  77. command interpreter takes control and prompts with *.  Now I
  78. can type in debugger commands.
  79.  
  80. 2. Debugger Commands
  81.  
  82. Debugger commands are one letter optionally followed by one or
  83. more arguments.  The command letter can be upper or lower case.
  84.  
  85. 2.1 Display a symbol
  86.  
  87. S [<symbol>] [<hex number>]
  88.  
  89. The S command is to set or display symbols.  S without arguments
  90. displays the whole symbol table.  S with one argument displays
  91. the current value of that symbol.  The value of a symbol is usually
  92. the address of a data item or function entry point.
  93. If a hex number is specified, the symbol is assigned a new value.
  94. If the symbol did not previously exist,  it is created at this
  95. time. Examples:
  96.  
  97.     *S main
  98.     02C2 main
  99.  
  100. If the symbol has been designated as a break point,  the B character
  101. preceeds the value.  Normally this command is used to check which
  102. symbols exist and what their current state is.  Symbols are loaded
  103. from the DEBUG.SYM file at the beginning of program execution so
  104. it is rare that new symbols need be defined.
  105.  
  106. 2.2 Display contents of memory
  107.  
  108. D [<symbol>] [<format string>]
  109.  
  110. The D command is used to display contents of memory.
  111. If D is used with a symbol argument,  the contents of that memory
  112. location are displayed.  Optionally one may specify a format string to
  113. be used to display the memory contents. (see below).
  114. If D is used without arguments, the contents of memory for each
  115. symbol in the data segment are displayed.  Examples:
  116.  
  117.     *D stdin
  118.     stdin=4548
  119.  
  120.     *D stdin input file is %d
  121.     stdin=input file is 17736
  122.  
  123. 2.3 Specify Format String for a Symbol
  124.  
  125. F <symbol> [<format string>]
  126.  
  127. The F command is used to specify the format string to used to display
  128. the contents addressed by the symbol.  This format string is used
  129. by a printf() statement whenever the contents of this memory location
  130. are displayed.  One memory location will be displayed for
  131. each % character in the format string.  If no format string has been
  132. associated with a symbol, a default format string of %x is assumed.
  133. The S command can be used to display a symbols current format string.
  134.  
  135. Format strings are the same ones used by the printf() statement except
  136. that the *, (, and ) characters have a special meaning.  They are used
  137. to permit the formated display of the objects of pointers.
  138. The * character preceeding a % character indicates that the object to
  139. be displayed is pointed to by the contents of memory.
  140. As an example, take the following program fragment:
  141.  
  142.     int a[] = {1, 2, 3,},    /* array */
  143.         *b,        /* pointer to an integer */
  144.         *c;        /* pointer to array */
  145.     ...
  146.     *b = 0;
  147.     c[0] = 4; c[1] = 5; c[2] = 6;
  148.  
  149. appropriate debugger commands might be:
  150.  
  151.     *D a a[0]=%d, a[1]=%d, a[2]=%d
  152.     a a[0]=1, a[1]=2, a[2]=3
  153.  
  154.     *D b &b=%x
  155.     b &b=5F6A
  156.  
  157.     *D b b=*%d
  158.     b 0
  159.  
  160.     *D c *(c[0]=%d, c[1]=%d, c[2]=%d)
  161.     c c[0]=4, c[1]=5, c[2]=6
  162.  
  163. The ( character pushes the next address on to a stack and loads
  164. the pointed to address.  The ) character recovers the saved address.
  165. Parenthesis can be used when the data structures to be displayed
  166. are more complex.  For example,  we might store a series of words
  167. as an array of linked lists.
  168.  
  169.     struct word {
  170.         struct word *next, *prev;
  171.         char    *spelling;
  172.     }
  173.  
  174.     struct word *chain[ARRAYSIZE];
  175.  
  176. The command to display the first three pointers would be:
  177.  
  178.     *D chain %x %x %x
  179.     chain 4556 4578 45A9
  180.  
  181. To display the first structure in each of the first two chains:
  182.  
  183.     *D chain *(nxt=%x, prv=%x, %s), *(nxt=%, prv=%x, %s)
  184.     chain (nxt=5543, prv=554B, martha), (nxt=558B, prv=83BF, anne)
  185.  
  186. To display the first two structures in the first chain:
  187.  
  188.     *D chain *(next=*(%x %x %s) prev=%x %s)
  189.     chain (next=(4384 340D george) prev=89E4 martha)
  190.  
  191. When a symbol corresponds to a function entry point,  the format
  192. string is used to display the arguments when
  193. the function is entered and return value when the function exited.
  194. The first % or pair of parenthesis is used as the format for the
  195. return value while the rest of the format string is used for the
  196. arguments.  For example, if the program contains:
  197.  
  198.     fp = fopen("LST:","r");
  199.  
  200. and the following F debugger command is specified:
  201.  
  202.     *F fopen file pointer=%x filename=%s mode=%s
  203.  
  204. the following will be displayed as the program executes:
  205.  
  206.     >fopen filename=LST: mode=r
  207.     ...
  208.     <fopen=file pointer=457D
  209.  
  210. One final tricky example on the format string:
  211.  
  212.     a[] = "abc";
  213.     b = "abc";
  214.  
  215. a and b cannot be displayed with same format string.  The correct
  216. format strings are:
  217.  
  218.     *D a %c%c%c
  219.     a abc
  220.  
  221.     *D b %s
  222.     b abc
  223.  
  224. This fooled me when I first started using the debugger.
  225. Think about it.
  226.  
  227. The F, D, and B commands can be used to assign a format string to a
  228. symbol.  In all cases the most recently specified format string
  229. becomes the default format string.  The F command can be used
  230. to reinitialize the format string for a symbol to null.
  231.  
  232. 2.4 Set/Reset Trace Mode
  233.  
  234. T
  235.  
  236. Program flow is traced when trace mode is on.  This means that
  237. as each function is entered and exited its name, arguments
  238. and return value are displayed.  In order for arguments to be
  239. displayed a format string must have been previously supplied
  240. for the function.
  241.  
  242. 2.5 Set/Reset break point
  243.  
  244. B [<symbol>] [<format string>]
  245.  
  246. The indicated symbol becomes a break point.
  247. This means that when the function corresponding to the symbol
  248. is entered,  normal execution will be suspended, the
  249. name of the function with its arguments will be displayed,
  250. and the debugger will accept commands from the console.
  251. The break point can be cleared by reissueing the B command.
  252. If the B command is issued without arguments, the B command
  253. is applied to every symbol in the code segment.
  254. Any number of symbols may be simultaneously designated as
  255. break points.
  256.  
  257. 2.6 Continue Execution
  258.  
  259. G [<number of break points to ignore>]
  260.  
  261. This command will continue execution from where it was last suspended.
  262. A simple G command will continue execution until a break
  263. point is encountered.  If G is followed by a number,
  264. that number of break points will be ignored before execution
  265. is again suspended.
  266.  
  267. 2.7 Walkback
  268.  
  269. W
  270.  
  271. This command displays the names and arguments of all the functions
  272. which have been entered but not yet exited.  This provides a clear
  273. picture of how we got to where we are in the program.  For example:
  274.  
  275.     *W
  276.     >main
  277.      >printf Hello World
  278.       >putc H
  279.     *
  280.  
  281. In order to have the function arguments displayed, format
  282. strings should be assigned to each symbol.  Format
  283. strings can be reassigned and the W command reissued as many times
  284. as desired.
  285.  
  286. 2.8 Load Symbol Table File
  287.  
  288. L <filename>
  289.  
  290. If the filename has no . character,  .SYM is appended.
  291. The file is opened and the symbols created.
  292. This command is used to load a symbol table file whose name might not
  293. be DEBUG.SYM .
  294.  
  295. 2.9 Process Debugger Commands from a Disk File
  296.  
  297. C <filename>
  298.  
  299. If the filename has no . character, .DBG is appended.  The file is
  300. opened and lines of text are processed as if they were read from the
  301. console.  This can be useful for automating repetitive commands.
  302. I use it for loading format strings for the functions in
  303. my C library.  Debug command files can be nested 3 deep.
  304.  
  305. Figure 1 contains a summary of the debugger commands.  Figure 2
  306. shows a very simple sample debugger session.
  307.  
  308. 3. How the Debugger Works
  309.  
  310. Figure 3 shows how the debugger operates:
  311.  
  312. 3.1 First the program is loaded to memory by the operating system.
  313. Memory can now classified into 7 segments:
  314.  
  315. a) page 0 - used by CP/M for certain system wide information.
  316.  
  317. b) Code Segment - contains machine language code.
  318.  
  319. c) Data Segment - contains memory for static variables.
  320.  
  321. d) Free Memory - to be reserved and unreserved by malloc() and free()
  322. functions.
  323.  
  324. e) Stack Area - used for storing function arguments, return
  325. addresses on function calls, and local variables.  The Stack
  326. area itself is divided into two areas: the unused portion
  327. beginning at the end of the Free Memory and the used portion
  328. beginning at the address contained by the stack pointer register.
  329.  
  330. f) ZRDOS or CP/M - Operating system code and data
  331.  
  332. g) BIOS - More operating system code and data
  333.  
  334. This arrangement is the most common but details may
  335. vary depending on the software you are using.
  336. The operating system segments are not shown in the figure as they
  337. are not relevant to the operation of the debugger.
  338.  
  339. 3.2 The function setup() is called before the program starts
  340. execution.  This function reads a symbol file from disk and
  341. loads it into memory.  The function loadsym()
  342. checks each symbol to determine whether it is in the code or data
  343. segment.  Each address in the code segment
  344. is assumed to be a function entry point.  The first five bytes
  345. of each entry point is stored in the symbol table and replaced
  346. in the program with a "call trap()" instruction.  Following
  347. the "call trap" instruction the address of the symbol table entry
  348. is stored.  The function comand() is called to permit the operator
  349. to issue debugger commands before the program starts to execute.
  350.  
  351. 3.3 When the operator executes the G command, comand() and setup()
  352. return and execution of the program begins.  When a function
  353. (say fprintf()) is called, arguments are pushed on the stack in
  354. reverse order,  the return address is pushed on the stack and
  355. execution jumps to the entry point for the fprintf() function.
  356.  
  357. 3.4 However, on arriving at the fprintf() entry point a "call trap()"
  358. instruction is encountered.  The address just beyond the
  359. "call trap()" instruction is pushed on the stack and execution
  360. jumps to trap().
  361.  
  362. 3.5 trap() receives control when any function is entered.
  363. First the return address on the top of the stack is adjusted to
  364. the original entry point of the interrupted function.  This will
  365. insure when trap() returns execution will continue on its original
  366. intended path.  The first five bytes of the entry point are reloaded
  367. with their original contents thereby replacing the "call trap"
  368. instruction.  This is done by the dactbp() function. The original
  369. return address is saved and replace with the address of the resume()
  370. function.  This will ensure that when fprintf() returns it will
  371. return to resume instead of the original caller.  Finally if
  372. the trace flag is on or the entry point is a break point the
  373. entry point symbol and arguments are displayed.
  374. If the function is a break point and no more breakpoints are to
  375. be ignored command mode is entered.
  376.  
  377. 3.6 When trap() returns, execution jumps to the orginal entry point
  378. which contains the original code.  We can now see why this debugger
  379. will not fully trace execution of recurrsive code.  The "call trap()"
  380. instruction at the entry point of fprintf() will not be restored
  381. until fprintf() returns.
  382.  
  383. 3.7 fprintf() does not return to the orginal return address but to
  384. function resume().  resume() restores the "call trap()" istruction
  385. in the function entry point, leaves the original return address
  386. on top of the stack and the value returned by fprintf() in the
  387. registers.  resume() returns to fprintf().  Neither the operation of
  388. fprintf() nor its caller has been altered in any way by the
  389. interruptions of the debugger.
  390.  
  391. 4. Installing the Debugger in Another Environment.
  392.  
  393. When moving to debugger to another environment, the first thing
  394. to review is the function of the stack, function parameter
  395. passing, and storage of local arguments.  On my machine (HD64180)
  396. the stack grows from high addresses to low addresses.  This
  397. affects the expressions used for retrieving and altering data on
  398. the stack in the trap() and resume() routines.
  399.  
  400. Q/C allocates storage for local variables on the stack
  401. such that the first local variable is stored in the lowest address
  402. and subsequent local variables are stored in higher addresses.
  403. This is very similer to storage of function parameters.
  404. farg() and ddata() are dependent on this arrangement.  If your
  405. compiler and/or linker do not function in this way,  these
  406. functions will have to be modified slightly.
  407.  
  408. Q/C allocates 1 byte for character variables in memory,  but two
  409. bytes when a character variable is pushed on the stack as a
  410. function argument.  Q/C floats and longs are twice as long as
  411. integers.  Sizes of objects may vary among environments.
  412. farg() should be altered accordingly.
  413.  
  414. Functions must be long enough so that "call trap()" followed
  415. by the symbol table entry address does not extend past the end
  416. of the function.  In my environment,  this means that
  417. functions must be at least 5 bytes long.
  418.  
  419. There must be a way to determine if an address is a function
  420. entry point or a data area.  I my environment this is done by
  421. comparing the address with the start of the data segment.
  422. Since we always link in the DEBUG.REL module before any others,
  423. the first static data item is stored at the beginning of the
  424. data segment.  By taking the address of this item with the & operator
  425. we have the start of the data segment.
  426.  
  427. The debugger calls some functions which might not be in
  428. your library yet.  These include the symbol table functions symmk(),
  429. symadd(), symlkup() and symdat() described in a previous article.
  430. The function strlwr() converts a string to lower case and returns
  431. a pointer to the string.  isatty() returns true if the file number
  432. arguments corresponds to a terminal, and strchr() returns a pointer
  433. to the first occurance of a given character in a string.
  434. (This used to be called index() in the standard library).
  435. To implement this debugger these functions must be included.
  436.  
  437. Possibly the most bothersome aspect of installing DEBUG is working
  438. out a method for passing control to the setup routine before
  439. execution of the program itself starts.  One easy way would be
  440. to modify the main() function to explicitly call setup() before
  441. it does anything else.  I wanted to avoid having to recompile
  442. modules just to use the debugger so I took a different approach.
  443. Q/C includes a function called _shell() which is used to open
  444. standard files and initialize arguments for the main() function.
  445. _shell() is executed just after the program is loaded.  Since
  446. the Q/C package includes the code for the _shell() function
  447. I modified it to call an external function setup() before
  448. execution was passed to main().  Also I added a module to the
  449. C library named setup() which contains only a return instruction.
  450. When a program is linked without DEBUG, _shell() calls setup()
  451. which returns inmediatly.  When DEBUG is included on the link
  452. command line,  _shell() calls setup() which intializes the
  453. debugger and permits setting of break points even before main() is
  454. called.  This scheme adds four bytes to every program I link
  455. (For the extraneous "call setup()" and return instruction), but
  456. means I don't have to recompile anything to use the debugger.
  457. This approach will be useful when I add an execution time
  458. profiler to my C compiler.  You'll have to workout your own
  459. scheme for passing control to setup().
  460.  
  461. Finally, the debugger calls some functions which might not be in
  462. your library yet.  These include the symbol table functions symmk(),
  463. symadd(), symlkup() and symdat() described in a previous article.
  464. The function strlwr() converts a string to lower case and returns
  465. a pointer to the string.  isatty() returns true if the file number
  466. arguments corresponds to a terminal, and strchr() returns a pointer
  467. to the first occurance of a given character in a string.
  468. (This used to be called index() in the standard library).
  469. To implement this debugger these functions must be included.
  470.  
  471. If your C compiler needs a source level debugger,  I'd be flattered
  472. if you'd consider the one presented here.
  473.  
  474. Bibliography
  475.  
  476. O'Connell, Patrick,  Relocating Macro Assembler and Linker for
  477. Z80 and HD64180. Echelon Inc. 101 First Street, Los Altos CA 94022
  478.  
  479. Colvin, Jim Q/C Users Manual. The Code Works.  Santa Barbara,
  480. California.
  481. .nf
  482. .bp
  483. Figure 1    Table of Debugger Commands
  484.  
  485. 1. S [<symbol>] [<hex number>]        Display a symbol
  486.  
  487. 2. D [<symbol>] [<format string>]    Display contents of memory
  488.  
  489. 3. F <symbol> [<format string>]        Specify Format String for a
  490.                     Symbol
  491.  
  492. 4. T                    Set/Reset Trace Mode
  493.  
  494. 5. B [<symbol>] [<format string>]    Set/Reset break point
  495.  
  496. 6. G [<count of ignored break points>]    Continue Execution
  497.  
  498. 7. W                    Walkback (Display Stack)
  499.  
  500. 8. L <filename>                Load Symbol Table File
  501.  
  502. 9. C <filename>                Process Debugger Commands from
  503. .bp
  504. Figure 2    Sample Debugging Session
  505.  
  506.     *F printf %x %s
  507.     *F putc %c char=%c fp=%x
  508.     *F bdos1 %x function#=%x arg=%x
  509.     *F exit %x
  510.     *F fclose %x fp=%x
  511.     *F close %x fd=%x
  512.     *T
  513.     trace on
  514.     *G
  515.      >main
  516.       >printf Hello World
  517.     
  518.        >_fmt
  519.         >putc char=H fp=5340
  520.          >bdos1 function#=2 arg=48
  521.          <bdos1 = 0
  522.         <putc = H
  523.         >putc char=e fp=5340
  524.          >bdos1 function#=2 arg=65
  525.          <bdos1 = 0
  526.         <putc = e
  527.         >putc char=l fp=5340
  528.          >bdos1 function#=2 arg=6C
  529.          <bdos1 = 0
  530.         <putc = l
  531.         >putc char=l fp=5340
  532.          >bdos1 function#=2 arg=6C
  533.          <bdos1 = 0
  534.         <putc = l
  535.         >putc char=o fp=5340
  536.          >bdos1 function#=2 arg=6F
  537.          <bdos1 = 0
  538.         <putc = o
  539.         >putc char=  fp=5340
  540.          >bdos1 function#=2 arg=20
  541.          <bdos1 = 0
  542.         <putc =  
  543.         >putc char=W fp=5340
  544.          >bdos1 function#=2 arg=57
  545.          <bdos1 = 0
  546.         <putc = W
  547.         >putc char=o fp=5340
  548.          >bdos1 function#=2 arg=6F
  549.          <bdos1 = 0
  550.         <putc = o
  551.         >putc char=r fp=5340
  552.          >bdos1 function#=2 arg=72
  553.          <bdos1 = 0
  554.         <putc = r
  555.         >putc char=l fp=5340
  556.          >bdos1 function#=2 arg=6c
  557.          <bdos1 = 0
  558.         <putc = l
  559.         >putc char=d fp=5340
  560.          >bdos1 function#=2 arg=65
  561.          <bdos1 = 0
  562.         <putc = d
  563.         >putc char=
  564.      fp=5340
  565.          >bdos1 function#=2 arg=D
  566.          <bdos1 = 0
  567.         <putc = 
  568.     
  569.         >putc char=
  570.              fp=5340
  571.          >bdos1 function#=2 arg=A
  572.          <bdos1 = 0
  573.        <putc =
  574.     
  575.       <_fmt
  576.      <printf = 0
  577.     <main
  578.     >exit
  579.     
  580.      >fclose fp=5318
  581.      <fclose = 0
  582.      >fclose fp=5322
  583.      <fclose = 0
  584.      >fclose fp=532C
  585.      <fclose = 0
  586.      >fclose fp=5336
  587.      <fclose = 0
  588.      >fclose fp=5340
  589.       >close fd=6
  590.        >_gfcb
  591.        <_gfcb
  592.       <close = 0
  593.      <fclose = 0
  594.      >fclose fp=534A
  595.      <fclose = 0
  596.      >fclose fp=5354
  597.      <fclose = 0
  598.      >fclose fp=535E
  599.      <fclose = 0
  600.      >fclose fp=5368
  601.      <fclose = 0
  602.      >fclose fp=5372
  603.      <fclose = 0
  604.     >bdos1 function#=0 arg=0
  605. =6
  606.        >_gfcb
  607.        <_gfcb
  608.